// Used for recognizing and parsing QR code images
import { codeClass } from './libvbar-m-dxcode.so'
import dxMap from './dxMap.js'
import * as os from "os"
import dxCommon from './dxCommon.js';
import bus from './dxEventBus.js'
const code = {}
const map = dxMap.get('default')
const codeObj = new codeClass();

/**
 * Image retrieval module initialization
 * @param {object} options Most configuration parameters can use the default values.
 *      @param {string} options.path                 Required: The path to the image acquisition device. This path varies depending on the device; for example, the value for DW200 is '/dev/video11'
 *      @param {number} options.width                Optional field: Image width (default is 0).
 *      @param {number} options.height               Optional field, image height, default is 0.
 *      @param {number} options.widthbytes           Optional field. Number of bytes per pixel: GREY: 1, YUV: 2, DW200 defaults to 1
 *      @param {number} options.pixel_format          Optional, pixel format, default is 1497715271 representing V4L2_PIX_FMT_GREY
 *      @param {number} options.max_channels          Optional. Maximum supported number of synchronous output channels; default is 3.
 *      @param {number} options.rotation             Optional field: Rotation angle. The default value is 90 degrees.
 *      @param {number} options.frame_num             Optional field, frame number, default is 3
 *      @param {string} options.capturerDogId         Optional field, camera watchdog handle
 */
code.capturerInit = function (options) {
    if (options.path === undefined || options.path === null || options.path.length < 1) {
        throw new Error("dxCode.init: 'path' parameter should not be null or empty")
    }
    let pointer = codeObj.capturerInit(options);
    if (!pointer) {
        throw new Error("dxCode.init: init failed")
    }
    os.sleep(100)
    let capturerDogPointer = dxCommon.handleId("watchdog", options.capturerDogId)
    codeObj.capturerRegisterCallback(pointer, "decoderCapturerImage", capturerDogPointer)
    dxCommon.handleId("code", "capturerid", pointer)
}


/**
 * Graphics decoding module initialization 
 * @param {object} options Most configuration parameters can use the default values.
 *      @param {string} options.name         Required field. Custom decoder name, can be anything.
 *      @param {number} options.width        Required field: Image width. This varies depending on the device; for example, it's 800 pixels on a DW200.
 *      @param {number} options.height       Required field. Image resolution varies depending on the device; for example, DW200 is 600.
 *      @param {number} options.widthbytes   Optional field. Number of bytes per pixel: GREY: 1, YUV: 2, default is 1.
 *      @param {object} options.config       This is not a required field; the default value is {}.
 *      @param {number} options.max_channels Optional. Maximum supported number of synchronous output channels; default is 10.
 */
code.decoderInit = function (options) {
    if (options.name === null || options.name.length < 1) {
        throw new Error("dxCode.init: 'name' parameter should not be null or empty")
    }
    if (options.width === undefined || options.width === null) {
        throw new Error("dxCode.init: 'width' parameter should not be null")
    }
    if (options.height === undefined || options.height === null) {
        throw new Error("dxCode.init: 'height' parameter should not be null")
    }
    _setDefaultOptions(options, 'config', {});
    _setDefaultOptions(options, 'widthbytes', 1);
    _setDefaultOptions(options, 'maxChannels', 10);
    let pointer = codeObj.decoderInit(options.name, options.config, options.width, options.widthbytes, options.height, options.maxChannels);
    if (!pointer) {
        throw new Error("dxCode.init: init failed")
    }
    os.sleep(100)
    codeObj.decoderCbRegister(pointer, "decoderOut")
    dxCommon.handleId("code", "decoderid", pointer)
    return pointer
}


/**
 * Graphics decoding module configuration update
 * @param {object} options Most configuration parameters can use the default values.
 *      @param {string} options.decoder         Optional field: Decoding engine type
 *      @param {number} options.deType          Optional field, encoding type
 *      @param {number} options.sMode           Optional, filtering based on code content                                                                                                                                                                                  │  
                                                │         default s_mode = 0                                                                                                                                                                                     │  
                                                │         0 : Interval pattern of the same code                                                                                                                                                                              │  
                                                │         1 : Single mode                                                                                                                                                                                        │  
                                                │         2 : Different code interval patterns   
 *      @param {number} options.interval        Optional field, interval time in interval mode 
 *      @param {object} options.searchTimeout   Optional field, timeout period for the search code
 *      @param {object} options.decoderTimeout  Optional field: Decoding timeout
 *      @param {number} options.searchMode      Optional, the strategy corresponding to the decoding engine
 *      @param {object} options.decoderMode     Optional field: Decoding engine feature configuration
 *      @param {number} options.qrMode          This is not a required field; the QR code configuration parameter is not publicly available by default.                                                                                                                                                                        │  
                                                │         default qr_mode = 15                                                                                                                                                                                   │  
                                                │         bit0 : Supports QR codes with small image size                                                                                                                                                                       │  
                                                │         bit1 : Supports QR codes with non-square anchors; this feature is disabled by default.                                                                                                                                                         │  
                                                │         bit2 : Enhanced QR code recognition is available for medical, abnormal, and complex scenarios; it is not required for general scenarios.                                                                                                                                       │  
                                                │         bit3 : QR code enhancement (time-consuming operation), can be left off in normal scenarios.                                                                                                                                                     │  
                                                │         bit4 : Supports QR codes without a quiet zone (time-consuming operation), not enabled by default.
 *      @param {object} options.decoderDelay    Optional field, delay between two decoding operations
 */
code.decoderUpdateConfig = function (options) {
    if (options === null) {
        throw new Error("dxCode.decoderUpdateConfig: 'options' parameter should not be null or empty")
    }
    let pointer = dxCommon.handleId("code", "decoderid")
    codeObj.decoderUpdateConfig(pointer, options)
    return pointer
}

/**
 * Decoder registration special code callback
 */
code.decodeSpecialCBRegister = function () {
    let pointer = dxCommon.handleId("code", "decoderid")
    let cbType = typeof codeObj.decodeSpecialCBRegister
    if (cbType === "function") {
        return codeObj.decodeSpecialCBRegister(pointer)
    }
}

/**
 * @param {object} options Most configuration parameters can use the default values.
 * @param {number} options.method Optional, isp main function
 * @param {number} options.sub_method Optional field, isp sub func
 * @param {number} options.target_luminance Optional field: Image target brightness value
 * @param {number} options.target_percentile Optional field, target brightness percentage
 * @param {number} options.sample_gap Optional field; sampling interval for calculation.
 * @param {number} options.min_exp Optional field, minimum exposure value
 * @param {number} options.max_exp Optional, maximum exposure value
 * @param {number} options.min_gain Optional, minimum gain value
 * @param {number} options.max_gain Optional, maximum gain value
 * @returns true/false
 */
code.capturerUpdateIspConfig = function (options) {
    if (options === null) {
        throw new Error("dxCode.capturerUpdateIspConfig: 'options' parameter should not be null or empty")
    }
    let pointer = dxCommon.handleId("code", "capturerid")
    return codeObj.capturerUpdateIspConfig(pointer, options)
}

/**
 * Start image retrieval listening thread
 * @param {string} id1 Required, serial port ID
 * @param {number} timeout Optional field. Timeout period (milliseconds): If no image retrieval instruction is received within the specified time, image retrieval will automatically stop.
 * @returns undefined
 */
code.startGetImageListen = function (id1, timeout = 1000) {
    if (id1 === null || id1 === undefined) {
        throw new Error("dxCode.startGetImageListen: 'id1' parameter should not be null or empty")
    }
    return codeObj.startGetImageListen(dxCommon.handleId("uart", id1), dxCommon.handleId("code", "capturerid"), timeout)
}

/**
 * Stop the image acquisition listening thread and only listen for the 5c/5d commands of the micro-light protocol. Sending any other commands during the process will stop the image acquisition thread.
 * @returns undefined
 */
code.stopGetImageListen = function () {
    return codeObj.stopGetImageListen()
}

/**
 * Get image capture status
 * @returns true/false
 */
code.getImageListenStatus = function () {
    return codeObj.getImageListenStatus()
}

/**
 * Check if the decoder message queue is empty.
 */
code.msgIsEmpty = function () {
    let pointer = dxCommon.handleId("code", "decoderid")
    return codeObj.msgIsEmpty(pointer)
}
/**
 * Read data from the decoder message queue.
 */
code.msgReceive = function () {
    let pointer = dxCommon.handleId("code", "decoderid")
    return codeObj.msgReceive(pointer)
}

function _setDefaultOptions(options, key, defaultValue) {
    if (options[key] === undefined || options[key] === null) {
        options[key] = defaultValue;
    }
}

/**
 * Determine if the values ​​of two Arraybuffers are the same.
 * @param {*} buffer1 
 * @param {*} buffer2 
 * @returns true/false
 */
function bufferIsEqual(buffer1, buffer2) {
    if (!buffer1 || !buffer2 || buffer1.byteLength !== buffer2.byteLength) {
        return false;
    }

    let view1 = new Uint8Array(buffer1);
    let view2 = new Uint8Array(buffer2);

    for (let i = 0; i < view1.length; i++) {
        if (view1[i] !== view2[i]) {
            return false;
        }
    }

    return true;
}

code.RECEIVE_MSG = '__code__MsgReceive'

/**
 * To simplify the use of the code component, the code is encapsulated in this worker. Users only need to subscribe to eventbus events to listen to them.
 * @param {object} options 
 *      @param {object} options.capturer  Capturer component parameters, refer to captureInit, are required.
 *      @param {object} options.decoder  Decoder component parameters, refer to decoderInit, are required.
 *      @param {number} options.mode  The default mode is interval mode, which means that duplicate QR codes will be reported repeatedly, with the reporting interval being interval. If it is 1, it is single-report mode, where duplicate QR codes will only be reported once.
 *      @param {number} options.interval  The scan interval is only meaningful when mode is 0; the default is 0.6 seconds.
 */

code.run = function (options) {
    if (!options || !options.capturer || !options.decoder) {
        throw new Error("dxcode.run:'options.capturer' and 'options.decoder' parameter should not be null or empty")
    }
    let init = map.get("__code__run_init")
    if (!init) {//Ensure initialization only once
        map.put("__code__run_init", options)
        bus.newWorker("__code", '/app/code/dxmodules/codeWorker.js')
    }
}

/**
 * If the controller is a single thread, you can directly use the `run` function, which will automatically start a thread.
 * If you want to join it to another existing thread, you can use the following wrapper functions.
 */
code.worker = {
    //Before the while loop
    beforeLoop: function (capturer, decoder) {
        code.capturerInit(capturer)
        code.worker.pointer = code.decoderInit(decoder)
        code.decodeSpecialCBRegister()
    },
    //In a while loop
    loop: function (mode = 0, interval = 600) {
        let pointer = code.worker.pointer
        if (!pointer) {
            pointer = dxCommon.handleId("code", "decoderid")
        }
        if (!codeObj.msgIsEmpty(pointer)) {
            let res = codeObj.msgReceive(pointer)
            if (res != undefined && res != null && res.length > 0) {
                res = JSON.parse(res)
                res.data = dxCommon.hexStringToArrayBuffer(res.data)
                const now = new Date().getTime()
                if (mode == 1) {//Single mode
                    if (!bufferIsEqual(res.data, code.worker.singleOldContent)) {
                        bus.fire(code.RECEIVE_MSG, res)
                        code.worker.lastTimestamp = now
                        code.worker.singleOldContent = res.data
                    }
                } else {//Interval mode 
                    let _interval = Math.max(300, interval)//At least 300 milliseconds
                    if ((now - code.worker.lastTimestamp) > _interval || !bufferIsEqual(res.data, code.worker.intervalOldContent)) {//Do not send duplicate data within 1 second
                        bus.fire(code.RECEIVE_MSG, res)
                        code.worker.lastTimestamp = now
                        code.worker.intervalOldContent = res.data
                    }
                }
            }
        }
    }
}

export default code;